ld 链接脚本中标号名称可能造成的问题
问题描述
在一款 risc-v 架构的开发板上,访问全局变量的时候出现了异常,调试发现全局变量的地址不正确。查看汇编发现全局变量的访问是通过 gp 寄存器中设定的全局指针加一个偏移量完成的,进一步查看这个寄存器的值,发现值不正确!
我的思考
gp 是【全局指针寄存器】,这个寄存器在我之前移植的另一款芯片上并没有使用到,这让我对这个寄存器没有足够重视。
不过这个问题应该是 gp 寄存器变量设定的值不对,可能只需要查看下给 gp 寄存器设定了什么值就能确定问题。
这个值是在启动脚本里面设定的,给它赋予了一个在链接脚本中设定的标号的值。调试启动脚本发现这个变量的值是正常的,初始化过程中 gp 的值确实设定为了一个正确的值,那问题在哪里呢?
进一步的思考我发现,这好像是在实时操作系统中的第一个任务运行之后发生的,而任务初始化时预先存储的现场里面也有对 gp 寄存器的设定,结果发现是这里设定的不对,可是为什么不对呢?
首先排除恢复现场时偏移量搞错的问题。
这之后我注意到在链接脚本与启动脚本(汇编代码)中赋值给 gp 寄存器的标号名称后面有个 $ 符号,而任务初始化过程中预先存储的现场中保存的待恢复的 gp 寄存器的值却没有引用这个 $ 符号。这是正常的!c 语言中变量名并不支持 $ 符号,但是链接脚本中的标号名称并没有指明名称可以使用字符的限定,我查看 ld 手册中与 PROVIDE 相关的章节并没有发现对标号名称所能使用的字符有什么限定,这时我意识到了问题。
之前为什么跑官方 demo 没有这种问题呢?
其实答案很简单!官方 demo 里面都是裸机,没有在 c 语言的代码中间接的设定 gp 寄存器,因此不会有问题。
问题解决
将标号后面的 $ 符号去掉,然后修改启动脚本。重新编译再次调试,终于正常了!
总结
链接脚本中设定的标号名称所能使用的字符没有明确限定,这意味着我们可以使用任意字符来设定标号。可是我们必须注意的是,如果这些标号要在 c 语言中进行引用,那么就必须以 c 语言中变量名称的限定来起名了,否则我们将无法引用这些标号!